﻿using System;
using System.Collections.Generic;
using System.Linq;
using System.Linq.Expressions;
using System.Text;
using Microsoft.MediaPlayer.Interop;
using System.Reflection;
using System.IO;
using System.Diagnostics;

namespace BMproductions.Media.MediaPlayer.WMPPlus {
	public class TagzFMT {

		public delegate string BuildDelegate(TagzFMT tagz, IWMPMedia3 media, object data);
		public delegate Expression BuildTagDelegate(string tag, ParameterExpression media, ParameterExpression data);

		BuildDelegate title;
		string format;
		BuildTagDelegate func;
		int usage;
		ParameterExpression mediaParam;
		ParameterExpression dataParam;
		ParameterExpression thisParam;

		static readonly string[] names = new string[] {
			"if", "if2", "if3", "decode",
			"ifgreater", "iflonger", "IfStrEqual2", "IfStrEqual", "IfStrNotEqual",
			"mod", "div", "mul", "muldiv", "sub", "add",
			"greater", "max", "min",
			"upper", "lower", "replace", "left", "right", "len",
			"substr", "strstr", "strrchr", "strlchr", "strchr",
			"shortest", "longest", "abbr",
			"padcut", "cut", "pad", "lpad", "trim",
			"repeat", "caps", "caps2",
			"fileext", "ext", "filepart", "filename", "directory",
			"split", "hex", "num", "dec", "null",
			"systime_second", "systime_minute", "systime_hour",
			"systime_day", "systime_month", "systime_year"
		};

		public string Format {
			get { return this.format; }
			set {
				if (this.format != value) {
					this.format = value;
					this.title = null;
				}
			}
		}

		public ParameterExpression MediaParameter {
			get { return this.mediaParam; }
		}

		public ParameterExpression DataParameter {
			get { return this.dataParam; }
		}

		#region Compiled functions
		bool IsEmpty(int before, string tag) {
			return before != this.usage;
		}

		string Empty(int before, string tag) {
			return before != this.usage ? tag : null;
		}

		string Check(string tag) {
			if (tag.IsNullEmpty())
				tag = String.Empty;
			else
				this.usage++;
			return tag;
		}

		string Brackets(int before, string expr) {
			return before != this.usage ? expr : String.Empty;
		}

		static int ParseInt(string str) {
			int res;
			if (String.IsNullOrEmpty(str) || !int.TryParse(str, out res))
				return 0;
			return res;
		}

		static string Left(string str, int len) {
			return str.Substring(0, len);
		}

		static string Right(string str, int len) {
			return str.Substring(str.Length - len);
		}

		static string SubStr(string str, int from, int to) {
			return str.Substring(from - 1, to - from + 1);
		}

		static int StrStr(string str, string str2) {
			return str.IndexOf(str2) + 1;
		}

		static int StrLChr(string str, string str2) {
			if (String.IsNullOrEmpty(str2))
				return 0;
			return str.IndexOf(str2[0]) + 1;
		}

		static int StrRChr(string str, string str2) {
			if (String.IsNullOrEmpty(str2))
				return 0;
			return str.LastIndexOf(str2[0]) + 1;
		}

		static string Shortest(string str1, string str2) {
			return str1.Length > str2.Length ? str2 : str1;
		}

		static string Longest(string str1, string str2) {
			return str1.Length > str2.Length ? str1 : str2;
		}

		static string Abbr(string str, int len) {
			if (str.Length < len)
				return str;
			string abbr = String.Empty;
			bool isfirst = true;
			bool hasabbr = false;
			for (int k = 0; k < str.Length; k++) {
				if (Char.IsWhiteSpace(str, k)) {
					isfirst = true;
					continue;
				}
				if (isfirst && (!hasabbr || !Char.IsUpper(str, k))) {
					hasabbr = false;
					continue;
				}
				abbr += str.Substring(k, 1);
				isfirst = false;
				hasabbr = true;
			}
			return abbr;
		}

		static string PadCut(string str, int len, string pad) {
			if (len < 1)
				return String.Empty;
			if (len <= str.Length)
				return Cut(str, len);
			else
				return Pad(str, len, pad);
		}

		static string Cut(string str, int len) {
			if (len < 1)
				return String.Empty;
			if (len >= str.Length)
				return str;
			return str.Substring(0, len);
		}

		static string Pad(string str, int len, string pad) {
			if (len <= str.Length)
				return str;
			if (String.IsNullOrEmpty(pad))
				pad = " ";
			if (pad.Length == 1)
				return str + new string(pad[0], str.Length - len);
			while (str.Length < len)
				str += pad;
			return str.Length > len ? str.Substring(0, len) : str;
		}

		static string LPad(string str, int len, string pad) {
			if (len <= str.Length)
				return str;
			if (String.IsNullOrEmpty(pad))
				pad = " ";
			if (pad.Length == 1)
				return new string(pad[0], str.Length - len) + str;
			while (str.Length < len)
				str = pad + str;
			return str.Length > len ? str.Substring(str.Length - len) : str;
		}

		static string Repeat(string str, int count) {
			if (count <= 0)
				return String.Empty;
			string res = str;
			while (count > 1) {
				res += str;
				count--;
			}
			return res;
		}

		static string Caps(string str) {
			StringBuilder sb = new StringBuilder(str);
			bool isfirst = true;
			for (int k = 0; k < str.Length; k++) {
				if (Char.IsWhiteSpace(str, k)) {
					isfirst = true;
					continue;
				}
				if (!isfirst)
					continue;
				sb[k] = Char.ToUpper(sb[k]);
			}
			return sb.ToString();
		}

		static string FileExt(string path) {
			string ext = Path.GetExtension(path);
			if (ext != null && ext.StartsWith("."))
				return ext.Substring(1);
			return ext;
		}

		static string Directory(string path, int level) {
			string dir = Path.GetDirectoryName(path);
			while (level > 1) {
				dir = Path.GetDirectoryName(dir);
				level--;
			}
			return Path.GetFileName(dir);
		}

		static string Split(string str, string sep, int index) {
			var strs = str.Split(new string[] { sep }, StringSplitOptions.None);
			if (index < 1 || index > strs.Length)
				return String.Empty;
			return strs[index - 1];
		}

		static string Hex(int num, int len) {
			return num.ToString("X" + len.ToString());
		}

		static string Dec(int num, int len) {
			return num.ToString("D" + len.ToString());
		}

		static int DatePart(string part) {
			var now = DateTime.Now;
			switch (part) {
			case "s":
				return now.Second;
			case "m":
				return now.Minute;
			case "H":
				return now.Hour;
			case "d":
				return now.Day;
			case "M":
				return now.Month;
			case "y":
				return now.Year;
			default:
				return 0;
			}
		}
		#endregion

		#region Lambda expression templates
		static readonly Expression<Func<string>> concatTemplate = () => String.Concat("", "");
		static readonly Expression<Func<TagzFMT, bool>> isemptyTemplate = (self) => self.IsEmpty(0, "");
		static readonly Expression<Func<TagzFMT, string>> emptyTemplate = (self) => self.Empty(0, "");
		static readonly Expression<Func<TagzFMT, string>> checkTemplate = (self) => self.Check("");
		static readonly Expression<Func<TagzFMT, string>> bracketsTemplate = (self) => self.Brackets(0, "");
		static readonly Expression<Func<int>> intTemplate = () => ParseInt("0");
		static readonly Expression<Func<string>> strTemplate = () => Convert.ToString(0);
		static readonly Expression<Func<int>> eqstrTemplate = () => String.Compare("", "", true);
		static readonly Expression<Func<int>> maxTemplate = () => Math.Max(0, 0);
		static readonly Expression<Func<int>> minTemplate = () => Math.Min(0, 0);
		static readonly Expression<Func<string, string>> upperTemplate = (str) => str.ToUpper();
		static readonly Expression<Func<string, string>> lowerTemplate = (str) => str.ToLower();
		static readonly Expression<Func<string, string>> replaceTemplate = (str) => str.Replace("", "");
		static readonly Expression<Func<string>> leftTemplate = () => Left("", 0);
		static readonly Expression<Func<string>> rightTemplate = () => Right("", 0);
		static readonly Expression<Func<string>> substrTemplate = () => SubStr("", 0, 0);
		static readonly Expression<Func<int>> strstrTemplate = () => StrStr("", "");
		static readonly Expression<Func<int>> strlchrTemplate = () => StrLChr("", "");
		static readonly Expression<Func<int>> strrchrTemplate = () => StrRChr("", "");
		static readonly Expression<Func<string>> shortestTemplate = () => Shortest("", "");
		static readonly Expression<Func<string>> longestTemplate = () => Longest("", "");
		static readonly Expression<Func<string>> abbrTemplate = () => Abbr("", 0);
		static readonly Expression<Func<string>> padcutTemplate = () => PadCut("", 0, " ");
		static readonly Expression<Func<string>> cutTemplate = () => Cut("", 0);
		static readonly Expression<Func<string>> padTemplate = () => Pad("", 0, " ");
		static readonly Expression<Func<string>> lpadTemplate = () => LPad("", 0, " ");
		static readonly Expression<Func<string, string>> trimTemplate = (str) => str.Trim();
		static readonly Expression<Func<string>> repeatTemplate = () => Repeat("", 0);
		static readonly Expression<Func<string>> capsTemplate = () => Caps("");
		static readonly Expression<Func<string>> extTemplate = () => FileExt("");
		static readonly Expression<Func<string>> nameTemplate = () => Path.GetFileName("");
		static readonly Expression<Func<string>> dirTemplate = () => Directory("", 1);
		static readonly Expression<Func<string>> splitTemplate = () => Split("", "", 0);
		static readonly Expression<Func<string>> hexTemplate = () => Hex(0, 0);
		static readonly Expression<Func<string>> decTemplate = () => Dec(0, 0);
		static readonly Expression<Func<int>> dateTemplate = () => DatePart("");
		static readonly MethodInfo concatMI = ((MethodCallExpression)concatTemplate.Body).Method;
		#endregion

		private delegate Expression AppendDelegate(Expression expr1, Expression expr2);
		private delegate Expression AppendStringDelegate(Expression expr, string text, AppendDelegate append);

		static string ExtractStr(Expression arg) {
			var conv = arg as UnaryExpression;
			var cnst = arg as ConstantExpression;
			if (cnst != null)
				return (cnst.Value ?? String.Empty).ToString();
			if (conv != null && conv.NodeType == ExpressionType.Convert)
				return ExtractStr(conv.Operand);
			return null;
		}

		static Expression AppendString(Expression expr, string text) {
			if (expr == null)
				return Expression.Constant(text);
			return AppendExpression(expr, Expression.Constant(text));
		}

		static Expression StatementError(string statement) {
			throw new Exception(String.Format("(invalid ${0} syntax)", statement));
		}

		static Expression AppendExpression(Expression expr1, Expression expr2) {
			var add = expr1 as BinaryExpression;
			var cnst1 = ExtractStr(expr1);
			var cnst2 = ExtractStr(expr2);
			if (add != null && cnst2 != null &&
				add.NodeType == ExpressionType.Add && add.Type == typeof(string)) {
				cnst1 = ExtractStr(add.Right);
				if (cnst1 != null) {
					expr1 = add.Left;
					expr2 = Expression.Constant(cnst1 + cnst2);
				}
			} else if (cnst1 != null && cnst2 != null)
				return Expression.Constant(cnst1 + cnst2);
			if (expr1 == null)
				return expr2;
			else if (expr2 == null)
				return expr1;
			return Expression.Add(expr1, expr2, concatMI);
		}

		public TagzFMT(string format, BuildTagDelegate func) {
			this.format = format;
			this.func = func;
			this.mediaParam = Expression.Parameter(typeof(IWMPMedia3), "media");
			this.dataParam = Expression.Parameter(typeof(object), "data");
			this.thisParam = Expression.Parameter(typeof(TagzFMT), "tagz");
			this.title = null;
		}

		static Expression Error(string message) {
			throw new Exception(message ?? "(syntax error in formatting string)");
		}

		static MethodInfo method(LambdaExpression temp) {
			return ((MethodCallExpression)temp.Body).Method;
		}

		static bool ExtractInt(ref Expression arg) {
			var conv = arg as UnaryExpression;
			var cnst = arg as ConstantExpression;
			int num;
			if (conv != null && conv.NodeType == ExpressionType.Convert &&
				conv.Method == method(strTemplate)) {
				arg = conv.Operand;
				return true;
			} else if (cnst != null && cnst.Type == typeof(string) &&
				int.TryParse((cnst.Value ?? String.Empty).ToString(), out num)) {
				arg = Expression.Constant(num);
				return true;
			}
			return cnst != null && cnst.Type == typeof(int);
		}

		static Expression ToInt(Expression arg) {
			var cond = arg as ConditionalExpression;
			if (ExtractInt(ref arg)) {
				return arg;
			} else if (cond != null) {
				var left = cond.IfFalse;
				var right = cond.IfTrue;
				if (ExtractInt(ref left) && ExtractInt(ref right)) {
					return Expression.Condition(cond.Test, left, right);
				}
			}
			return Expression.Convert(arg, typeof(int), method(intTemplate));
		}

		static UnaryExpression ToStr(Expression arg) {
			return Expression.Convert(arg, typeof(string), method(strTemplate));
		}

		Expression Compile(ref int index, params char[] endchars) {
			if (String.IsNullOrEmpty(format))
				return null;
			Expression res = null;
			char[] endchars2 = new char[] { '%', '$', '[', ']', '(', ')', '\'', '\r', '\n', ',' };
			while (index < format.Length) {
				int end = format.IndexOfAny(endchars2, index);
				if (end < 0)
					return AppendString(res, format.Substring(index));
				if (end > index) {
					res = AppendString(res, format.Substring(index, end - index));
					index = end;
				}
				if (endchars.Contains(format[index]))
					return res ?? Expression.Constant("");
				switch (format[index]) {
				case '%':
					#region Tag
					index++;
					if (format[index] == '%') {
						res = AppendString(res, "%");
						index++;
						break;
					}
					end = format.IndexOf('%', index + 1);
					if (end == -1) {
						return Error(null);
					}
					string tag = format.Substring(index, end - index);
					Expression etag = func(tag, mediaParam, dataParam);
					if (etag != null) {
						res = AppendExpression(res,
								Expression.Call(thisParam, method(checkTemplate), etag));
					}
					index = end + 1;
					break;
					#endregion
				case '$':
					#region Statement
					index++;
					if (format[index] == '$') {
						res = AppendString(res, "$");
						index++;
						break;
					}
					end = format.IndexOf('(', index + 1);
					if (end == -1) {
						return Error(null);
					}
					string stat = format.Substring(index, end - index);
					var args = new List<Expression>();
					Expression arg;
					index = end + 1;
					while ((arg = Compile(ref index, ',', ')')) != null) {
						if (index < 0) {
							return Error("(internal error)");
						}
						args.Add(arg);
						index++;
						if (format[index-1] == ')')
							break;
					}
					#region Function
					switch (stat.ToLower()) {
					case "if": // arg1 ? arg2 : arg3
						if (args.Count != 3)
							return StatementError(stat);
						arg = Expression.Condition(
								Expression.Call(thisParam, method(isemptyTemplate),
									Expression.Field(thisParam, "usage"), args[0]),
								args[1], args[2]);
						break;
					case "if2": // arg1 ?? arg2
						if (args.Count != 2)
							return StatementError(stat);
						arg = Expression.Coalesce(
								Expression.Call(thisParam, method(emptyTemplate),
									Expression.Field(thisParam, "usage"), args[0]),
								args[1]);
						break;
					case "if3": // arg1 ?? ... ?? argN
						if (args.Count < 2)
							return StatementError(stat);
						arg = Expression.Call(thisParam, method(emptyTemplate),
								Expression.Field(thisParam, "usage"), args[0]);
						for (int i = 1; i < args.Count - 1; i++)
							arg = Expression.Coalesce(arg,
									Expression.Call(thisParam, method(emptyTemplate),
										Expression.Field(thisParam, "usage"), args[i]));
						arg = Expression.Coalesce(arg, args[args.Count - 1]);
						break;
					case "decode": // arg1 == arg2 ? arg3 : arg1 == arg4 ? arg5 ... : argN
						if (args.Count < 2 || (args.Count & 1) != 0)
							return StatementError(stat);
						arg = args[args.Count - 1];
						for (int i = args.Count - 2; i >= 0; i -= 2) {
							arg = Expression.Condition(
									Expression.Equal(
										Expression.Call(method(eqstrTemplate),
											args[0], args[i], Expression.Constant(true)),
										Expression.Constant(0)),
									args[i + 1], arg);
						}
						break;
					case "ifgreater": // arg1 > arg2 ? arg3 : arg4
						if (args.Count != 4)
							return StatementError(stat);
						arg = Expression.Condition(
								Expression.GreaterThan(ToInt(args[0]), ToInt(args[1])),
								args[2], args[3]);
						break;
					case "iflonger": // arg1.Length > arg2 ? arg3 : arg4
						if (args.Count != 4)
							return StatementError(stat);
						arg = Expression.Condition(
								Expression.GreaterThan(
									Expression.Property(args[0], "Length"), ToInt(args[1])),
								args[2], args[3]);
						break;
					case "ifstrequal2": // arg1 == arg2 ? arg3 : arg4
					case "ifstrequal": // arg1 == arg2 ? arg3 : ""
						if (args.Count != 3 && (args.Count != 4 || String.Compare(stat, "ifstrequal", true) == 0))
							return StatementError(stat);
						arg = Expression.Condition(
								Expression.Equal(
										Expression.Call(method(eqstrTemplate),
											args[0], args[1], Expression.Constant(true)),
										Expression.Constant(0)),
								args[2], args.Count == 4 ? args[3] : Expression.Constant(""));
						break;
					case "ifstrnotequal": // arg1 != arg2 ? arg3 : ""
						if (args.Count != 3)
							return StatementError(stat);
						arg = Expression.Condition(
								Expression.NotEqual(
										Expression.Call(method(eqstrTemplate),
											args[0], args[1], Expression.Constant(true)),
										Expression.Constant(0)),
								args[2], Expression.Constant(""));
						break;
					case "mod": // arg1 % arg2
						if (args.Count < 2)
							return StatementError(stat);
						arg = ToInt(args[0]);
						for (int i = 1; i < args.Count; i++) {
							arg = Expression.Modulo(arg, ToInt(args[i]));
						}
						arg = ToStr(arg);
						break;
					case "div": // arg1 / arg2
						if (args.Count < 2)
							return StatementError(stat);
						arg = ToInt(args[0]);
						for (int i = 1; i < args.Count; i++) {
							arg = Expression.Divide(arg, ToInt(args[i]));
						}
						arg = ToStr(arg);
						break;
					case "mul": // arg1 * arg2
						if (args.Count < 2)
							return StatementError(stat);
						arg = ToInt(args[0]);
						for (int i = 1; i < args.Count; i++) {
							arg = Expression.Multiply(arg, ToInt(args[i]));
						}
						arg = ToStr(arg);
						break;
					case "muldiv": // arg1 * arg2 / arg3
						if (args.Count != 3)
							return StatementError(stat);
						arg = ToInt(args[0]);
						for (int i = 1; i < args.Count; i++) {
							arg = Expression.Multiply(arg, ToInt(args[i]));
						}
						arg = ToStr(Expression.Divide(Expression.Multiply(
										ToInt(args[0]), ToInt(args[1])), ToInt(args[2])));
						break;
					case "sub": // arg1 - arg2
						if (args.Count < 2)
							return StatementError(stat);
						arg = ToInt(args[0]);
						for (int i = 1; i < args.Count; i++) {
							arg = Expression.Subtract(arg, ToInt(args[i]));
						}
						arg = ToStr(arg);
						break;
					case "add": // arg1 + arg2
						if (args.Count < 2)
							return StatementError(stat);
						arg = ToInt(args[0]);
						for (int i = 1; i < args.Count; i++) {
							arg = Expression.Add(arg, ToInt(args[i]));
						}
						arg = ToStr(arg);
						break;
					case "greater": // arg1 > arg2 ? "1" : ""
						if (args.Count != 2)
							return StatementError(stat);
						arg = Expression.Condition(
								Expression.GreaterThan(ToInt(args[0]), ToInt(args[1])),
								Expression.Constant("1"), Expression.Constant(""));
						break;
					case "max": // Math.Max(arg1, arg2)
						if (args.Count < 2)
							return StatementError(stat);
						arg = ToInt(args[0]);
						for (int i = 1; i < args.Count; i++) {
							arg = Expression.Call(null, method(maxTemplate), arg, ToInt(args[i]));
						}
						arg = ToStr(arg);
						break;
					case "min": // Math.Min(arg1, arg2)
						if (args.Count < 2)
							return StatementError(stat);
						arg = ToInt(args[0]);
						for (int i = 1; i < args.Count; i++) {
							arg = Expression.Call(null, method(minTemplate), arg, ToInt(args[i]));
						}
						arg = ToStr(arg);
						break;
					case "upper":
						if (args.Count != 1)
							return StatementError(stat);
						arg = Expression.Call(args[0], method(upperTemplate));
						break;
					case "lower":
						if (args.Count != 1)
							return StatementError(stat);
						arg = Expression.Call(args[0], method(upperTemplate));
						break;
					case "replace":
						if (args.Count != 3)
							return StatementError(stat);
						arg = Expression.Call(args[0], method(replaceTemplate), args[1], args[2]);
						break;
					case "left":
						if (args.Count != 2)
							return StatementError(stat);
						arg = Expression.Call(null, method(leftTemplate), args[0], ToInt(args[1]));
						break;
					case "right":
						if (args.Count != 2)
							return StatementError(stat);
						arg = Expression.Call(null, method(rightTemplate), args[0], ToInt(args[1]));
						break;
					case "len":
						if (args.Count != 1)
							return StatementError(stat);
						arg = ToStr(Expression.Property(args[0], "Length"));
						break;
					case "substr":
						if (args.Count != 3)
							return StatementError(stat);
						arg = Expression.Call(null, method(substrTemplate), args[0], ToInt(args[1]), ToInt(args[2]));
						break;
					case "strstr":
						if (args.Count != 2)
							return StatementError(stat);
						arg = ToStr(Expression.Call(null, method(strstrTemplate), args[0], args[1]));
						break;
					case "strrchr":
						if (args.Count != 2)
							return StatementError(stat);
						arg = ToStr(Expression.Call(null, method(strrchrTemplate), args[0], args[1]));
						break;
					case "strlchr":
					case "strchr":
						if (args.Count != 2)
							return StatementError(stat);
						arg = ToStr(Expression.Call(null, method(strlchrTemplate), args[0], args[1]));
						break;
					case "shortest":
						if (args.Count < 2)
							return StatementError(stat);
						arg = args[0];
						for (int i = 1; i < args.Count; i++) {
							arg = Expression.Call(null, method(shortestTemplate), arg, args[i]);
						}
						break;
					case "longest":
						if (args.Count < 2)
							return StatementError(stat);
						arg = args[0];
						for (int i = 1; i < args.Count; i++) {
							arg = Expression.Call(null, method(longestTemplate), arg, args[i]);
						}
						break;
					case "abbr":
						if (args.Count != 1 && args.Count != 2)
							return StatementError(stat);
						arg = Expression.Call(null, method(abbrTemplate), args[0],
								args.Count == 2 ? ToInt(args[1]) : Expression.Constant(0));
						break;
					case "padcut":
						if (args.Count != 2 && args.Count != 3)
							return StatementError(stat);
						arg = Expression.Call(null, method(padcutTemplate), args[0], ToInt(args[1]),
								args.Count == 3 ? args[2] : Expression.Constant(" "));
						break;
					case "cut":
						if (args.Count != 2)
							return StatementError(stat);
						arg = Expression.Call(null, method(cutTemplate), args[0], ToInt(args[1]));
						break;
					case "pad":
						if (args.Count != 2 && args.Count != 3)
							return StatementError(stat);
						arg = Expression.Call(null, method(padTemplate), args[0], ToInt(args[1]),
								args.Count == 3 ? args[2] : Expression.Constant(" "));
						break;
					case "lpad":
						if (args.Count != 2 && args.Count != 3)
							return StatementError(stat);
						arg = Expression.Call(null, method(lpadTemplate), args[0], ToInt(args[1]),
								args.Count == 3 ? args[2] : Expression.Constant(" "));
						break;
					case "trim":
						if (args.Count != 1)
							return StatementError(stat);
						arg = Expression.Call(args[0], method(trimTemplate));
						break;
					case "repeat":
						if (args.Count != 2)
							return StatementError(stat);
						arg = Expression.Call(null, method(repeatTemplate), args[0], ToInt(args[1]));
						break;
					case "caps":
						if (args.Count != 1)
							return StatementError(stat);
						arg = Expression.Call(null, method(capsTemplate),
								Expression.Call(args[0], method(lowerTemplate)));
						break;
					case "caps2":
						if (args.Count != 1)
							return StatementError(stat);
						arg = Expression.Call(null, method(capsTemplate), args[0]);
						break;
					case "fileext":
					case "ext":
						if (args.Count != 1)
							return StatementError(stat);
						arg = Expression.Call(null, method(extTemplate), args[0]);
						break;
					case "filepart":
					case "filename":
						if (args.Count != 1)
							return StatementError(stat);
						arg = Expression.Call(null, method(nameTemplate), args[0]);
						break;
					case "directory":
						if (args.Count != 1 && args.Count != 2)
							return StatementError(stat);
						arg = Expression.Call(null, method(dirTemplate), args[0],
							args.Count == 2 ? ToInt(args[1]) : Expression.Constant(1));
						break;
					case "split":
						if (args.Count != 3)
							return StatementError(stat);
						arg = Expression.Call(null, method(splitTemplate), args[0], args[1], ToInt(args[2]));
						break;
					case "hex":
						if (args.Count != 1 && args.Count != 2)
							return StatementError(stat);
						arg = Expression.Call(null, method(hexTemplate), ToInt(args[0]),
							args.Count == 2 ? ToInt(args[1]) : Expression.Constant(0));
						break;
					case "num":
					case "dec":
						if (args.Count != 1 && args.Count != 2)
							return StatementError(stat);
						arg = Expression.Call(null, method(decTemplate), ToInt(args[0]),
							args.Count == 2 ? ToInt(args[1]) : Expression.Constant(0));
						break;
					case "null":
						arg = null;
						break;
					case "systime_second":
						if (args.Count != 0)
							return StatementError(stat);
						arg = Expression.Call(null, method(dateTemplate), Expression.Constant("s"));
						break;
					case "systime_minute":
						if (args.Count != 0)
							return StatementError(stat);
						arg = Expression.Call(null, method(dateTemplate), Expression.Constant("m"));
						break;
					case "systime_hour":
						if (args.Count != 0)
							return StatementError(stat);
						arg = Expression.Call(null, method(dateTemplate), Expression.Constant("H"));
						break;
					case "systime_day":
						if (args.Count != 0)
							return StatementError(stat);
						arg = Expression.Call(null, method(dateTemplate), Expression.Constant("d"));
						break;
					case "systime_month":
						if (args.Count != 0)
							return StatementError(stat);
						arg = Expression.Call(null, method(dateTemplate), Expression.Constant("M"));
						break;
					case "systime_year":
						if (args.Count != 0)
							return StatementError(stat);
						arg = Expression.Call(null, method(dateTemplate), Expression.Constant("y"));
						break;
					default:
						arg = null;
						res = AppendString(res, String.Format("(unknown function: ${0})", stat));
						continue;
					}
					#endregion
					res = AppendExpression(res, arg);
					break;
					#endregion
				case ',':
					res = AppendString(res, ",");
					index++;
					break;
				case '\'':
					#region String
					index++;
					if (format[index] == '\'') {
						res = AppendString(res, "'");
						index++;
						break;
					}
					end = format.IndexOf('\'', index + 1);
					if (end == -1) {
						return Error(null);
					}
					res = AppendString(res, format.Substring(index, end - index));
					index = end + 1;
					break;
					#endregion
				case '[':
					#region Brackets
					index++;
					arg = Compile(ref index, ']');
					if (arg != null)
						arg = Expression.Call(thisParam, method(bracketsTemplate),
								Expression.Field(thisParam, "usage"), arg);
					res = AppendExpression(res, arg);
					if (index != -1)
						index++;
					break;
					#endregion
				case ']':
				case '(':
				case ')':
					return Error(null);
				case '\r':
				case '\n':
					index++;
					break;
				default:
					res = AppendString(res, format.Substring(index, 1));
					index++;
					break;
				}
			}
			if (index >= format.Length)
				index = -1;
			return res ?? Expression.Constant("");
		}

		public Expression Compile() {
			if (this.title != null)
				return null;
			Expression expr;
			try {
				int index = 0;
				expr = Compile(ref index, new char[] { });
			}
			catch (Exception ex) {
				Trace.WriteLine(ex.Message);
				Debug.WriteLine(ex.ToString());
				expr = Expression.Constant(ex.Message);
			}
			var lambda = Expression.Lambda<BuildDelegate>(expr, this.thisParam, this.mediaParam, this.dataParam);
			this.title = lambda.Compile();
			return expr;
		}

		public string Build(IWMPMedia3 media, object data) {
			Compile();
			if (this.title == null)
				return String.Empty;
			this.usage = 0;
			try {
				return this.title(this, media, data);
			} catch (Exception ex) {
				Trace.WriteLine(ex.Message);
				Debug.WriteLine(ex.ToString());
				return ex.Message;
			}
		}

		public static string[] AvailableFunctions {
			get { return names; }
		}
	}
}
